package actors

import java.io.File
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{Behavior, PostStop}
import akka.stream.Materializer
import command.{CommandExec, MissionUtils}
import dao._
import implicits.Implicits._
import models.Tables.{MissionRow, WorkflowMissionRow}
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import play.api.i18n.Messages
import play.api.libs.ws.WSClient
import play.api.mvc.{AnyContent, Request, RequestHeader}
import shared.Pojo._
import shared.SharedTool
import tool.Pojo._
import tool.workflow.soft.AllSubSoft
import tool.{FormTool, Tool, WorkflowMissionFileTool}
import utils.Utils

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
import scalax.collection.mutable.Graph
import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._
import scalax.collection.GraphTraversal.BreadthFirst
import scalax.collection.edge.WDiEdge
import scalax.collection.edge.Implicits._
import shared.pojo.WorkflowMission.FsData
import tool.workflow.DataPreprocessWorkflow
import tool.workflow.fsSoft.AllFsSoft

import scala.collection.parallel.CollectionConverters._
import upickle.default._

/** Created by yz on 13/4/2020
  */
object WorkflowMissionExecutor extends WorkflowMissionFileTool {

  def featureSelection(
      workspaceDir: File,
      resultDir: File
  )(implicit dao: MyDao, ws: WSClient, materializer: Materializer, messages: Messages) = {
    val value = Utils.configFile2Json(workspaceDir)
    val data = value.transform(reader[FsData])
    val graphJson = ujson.read(data.graphData)
    val lines = graphJson("pens").arr.filter { x =>
      x("type").num == 1
    }
    val froms = lines.map { node =>
      node("from")("id").str
    }
    val tos = lines.map { node =>
      node("to")("id").str
    }
    val edges = froms
      .zip(tos)
      .map { case (from, to) =>
        from ~> to
      }
      .toList
    val graph = Graph(edges: _*)
    val topologNode = graph.topologicalSort.toOption.get
    val layerdNode = topologNode.toLayered.filterNot { case (i, nodes) =>
      nodes.toList.contains("dataFileNode")
    }
    val sortedNode = layerdNode.flatMap { case (i, nodes) =>
      nodes
    }.toList
    val indexMap = sortedNode
      .groupBy { node =>
        SharedTool.getKind(node)
      }
      .flatMap { case (kind, nodes) =>
        val sortNodes = nodes.sortBy { node =>
          SharedTool.getId(node)
        }
        nodes.map { node =>
          (node, sortNodes.indexOf(node))
        }
      }
    val basicDir = new File(workspaceDir, "dataFileNode").createDirectoryWhenNoExist
    Tool.getMatrixFile(workspaceDir).txtLines.toFile(Tool.getDiffMetaboliteFile(basicDir))
    val finalCommandExec = layerdNode.foldLeft(CommandExec()) { (commandExec, inLayeredNode) =>
      val nodes = inLayeredNode._2
      commandExec
        .parExec { b =>
          val commands = nodes.toList
            .map { node =>
              val kind = SharedTool.getKind(node)
              val index = indexMap(node)
              val curSoft = AllFsSoft.kindSoftMap(kind)
              val parentNodes = node.incoming.map { edge =>
                edge.from
              }

              val parentNodeStr = parentNodes.mkString(";")
              val subWorkspaceDir = new File(workspaceDir, node).createDirectoryWhenNoExist
              curSoft.produceInput(subWorkspaceDir, resultDir, index, parentNodeStr, node)
              curSoft.producePlotConfigFile(subWorkspaceDir, index)
              curSoft.getCommandData(subWorkspaceDir, resultDir, index)
            }
            .par
            .threadNum(4)
          commands
        }
        .map { b =>
          nodes.toList.foreach { node =>
            val kind = SharedTool.getKind(node)
            val index = indexMap(node)
            val curSoft = AllFsSoft.kindSoftMap(kind)
            val subWorkspaceDir = new File(workspaceDir, node).createDirectoryWhenNoExist
            curSoft.resultDeal(subWorkspaceDir, resultDir, index)
          }
        }
    }
    finalCommandExec.map { b =>
      val dirMap = Tool.featureSelectionResultDeal(workspaceDir, resultDir, graph = graph)
      new File(Tool.dataDir, "src").dirCopyToDir(resultDir)
      val htmlFile = new File(resultDir, "index.html")
      val htmlStr = views.html.user.workflow.viewHtml("src", data.graphData, write(dirMap)).toString()
      htmlStr.toFile(htmlFile)
    }
  }

  def apply(
      mission: WorkflowMissionRow
  )(implicit dao: MyDao, ws: WSClient, materializer: Materializer, messages: Messages): Behavior[MissionAction] =
    Behaviors.setup { context =>
      Behaviors
        .receive[MissionAction] { (context, message) =>
          message match {
            case RunAction =>
              implicit val missionDao = dao.workflowMissionDao
              implicit val implMissionDao = dao.missionDao
              val newMision = mission.copy(state = "running")
              val userId = mission.userId
              val outDir = getUserMissionKindDir(mission.userId, mission.kind)
              val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
              val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
              val logFile = Tool.getLogFile(workspaceDir.getParentFile)
              missionDao
                .update(newMision)
                .map { x =>
                  val commandExec = mission.kind match {
                    case "featureSelection" => featureSelection(workspaceDir, resultDir)
                  }

                  val state = if (commandExec.isSuccess) {
                    "success"
                  } else {
                    println("commandExec:" + commandExec.errorInfo)
                    commandExec.errorInfo.toFile(logFile)
                    "error"
                  }
                  val newMission = mission.copy(state = state, endTime = Some(new DateTime()))
                  missionDao.update(newMission).map { x => }
                }
                .onComplete {
                  case Failure(exception) =>
                    exception.printStackTrace()
                    println("Failure" + exception.toString)
                    FileUtils.writeStringToFile(logFile, exception.toString)
                    val newMission = mission.copy(state = "error", endTime = Some(new DateTime()))
                    missionDao.update(newMission).map { x => }
                    context.self ! Stop
                  case Success(x) =>
                    context.self ! Stop
                }
              Behaviors.same
            case Stop => Behaviors.stopped
          }
        }
        .receiveSignal { case (context, PostStop) =>
          Behaviors.stopped
        }
    }

}
